/// <reference path="../interfaces.ts" />

namespace FIREFLYX {
    /** 灯光类型 */
    export enum LightType {
        /** 方向光源 */
        DIRECTIONAL,
        /** 点光源 */
        POINT,
        /** 聚光灯 光源 */
        SPOT
    }

    /** 阴影类型 */
    export enum ShadowType {
        /** 没有阴影 */
        ON_SHADOW,
        /** 硬阴影 */
        HARD_SHADOW,
        /** 软阴影阴影 */
        SOFT_SHADOW,
    }

    /** 阴影纹理Map分辨率 */
    export enum ShadowMapResolution {
        /** 低 */
        LOW,
        /** 中 */
        MEDIUM,
        /** 高 */
        HIGH,
        /** 非常高 */
        VERY_HIGH
    }

    interface ILightEventMap {
        "type_set": Light;
        "color_set": Light;
        "intensity_set": Light;
        "range_set": Light;
        "spotAngle_set": Light;
    }

    /**
     * 灯光 视锥数据
     */
    export class LightFrustumData {
        /**
         * 构造 灯光视锥数据
         * @param near 近平面
         * @param far 远平面
         * @param w 宽度
         * @param h 高度
         * @param position 位置 
         * @param rotation 旋转
         */
        // eslint-disable-next-line no-useless-constructor
        constructor(public near = 0, public far = 0, public w = 0, public h = 0,
            public position = new Vector3(), public rotation = new Quaternion()) {
        }
    }

    /**
     * 光源组件
     */
    export class Light extends Component implements IEventListener {
        private static readonly HELP_MAT = new Matrix();
        private static readonly HELP_MAT_1 = new Matrix();
        private static readonly HELP_VEC3_ONE = new Vector3(1, 1, 1);
        private static readonly HELP_VEC3_ZERO = new Vector3(0, 0, 0);
        private static readonly HELP_VEC3 = new Vector3();
        private static readonly HELP_VEC3_1 = new Vector3();
        private static readonly HELP_VEC3_2 = new Vector3();
        //标记为light
        protected static _useSign: ComponentUseSign = "light";

        private _type: LightType = LightType.DIRECTIONAL;
        private _color: Color4 = new Color4();
        private _intensity: number = 1;
        private _range: number = 10;
        private _spotAngle: number = 30;
        private _eventDisp: EventDispatcher = new EventDispatcher();
        private _viewProjMatrixList: Matrix[] = [];
        /** 阴影类型 */
        public shadowType: ShadowType = ShadowType.HARD_SHADOW;
        /** 阴影图分辨率 */
        public shadowMapResolution: ShadowMapResolution = ShadowMapResolution.HIGH;
        /** 阴影图强度 0-1 */
        public shadowStrength: number = 1;
        /** 阴影近平面 */
        public shadowNear: number = 0.0;
        /** 基本偏移 */
        public shadowBias: number = 0.05;
        /** 法线方向偏移 */
        public shadowNormalBias: number = 0.4;

        /** 光源类型 */
        public get type() { return this._type; }
        public set type(val) {
            if (this._type == val) return;
            this._type = val;
            if (this._eventDisp.listenerCount(`type_set`) > 0) {
                this._eventDisp.dispatch(`type_set`, this);
            }
        }

        /** 光源颜色 , rgb 分量 范围 0-1 ，a 分量为 HDR 强度*/
        public get color() { return this._color; }
        public set color(val) {
            this._color = val;
            if (this._eventDisp.listenerCount(`color_set`) > 0) {
                this._eventDisp.dispatch(`color_set`, this);
            }
        }

        /** 光源强度 */
        public get intensity() { return this._intensity; }
        public set intensity(val) {
            if (this._intensity == val) return;
            this._intensity = val;
            if (this._eventDisp.listenerCount(`intensity_set`) > 0) {
                this._eventDisp.dispatch(`intensity_set`, this);
            }
        }

        /** 光源光照范围 , Point、Spot 类型有效  */
        public get range() { return this._range; }
        public set range(val) {
            if (this._range == val) return;
            this._range = val;
            if (this._eventDisp.listenerCount(`range_set`) > 0) {
                this._eventDisp.dispatch(`range_set`, this);
            }
        }

        /** 聚光灯 角度值 */
        public get spotAngle() { return this._spotAngle; }
        public set spotAngle(val) {
            if (this._spotAngle == val) return;
            this._spotAngle = val;
            if (this._eventDisp.listenerCount(`spotAngle_set`) > 0) {
                this._eventDisp.dispatch(`spotAngle_set`, this);
            }
        }

        /**
         * VP 矩阵 
         * @param index 级联索引
         * @returns 
         */
        public getViewProjMatrix(index: number) {
            if (index < 0) return null;
            if (!this._viewProjMatrixList[index]) {
                this._viewProjMatrixList[index] = new Matrix();
            }
            return this._viewProjMatrixList[index];
        }

        /**
         * 计算 方向光视锥数据
         * @param camFrustum 相机视锥(世界空间中的8个顶点位置坐标)
         * @param lightWRot 方向光世界空间旋转
         * @param out 输出视锥数据
         */
        public static calcDirLightFrustumData(camFrustum: Vector3[], lightWRot: Quaternion, out: LightFrustumData) {
            const max = Light.HELP_VEC3;
            max.set(-Number.POSITIVE_INFINITY, -Number.POSITIVE_INFINITY, -Number.POSITIVE_INFINITY);
            const min = Light.HELP_VEC3_1;
            min.set(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY);
            const tempV3 = Light.HELP_VEC3_2;
            const wRot = lightWRot;
            const light2WorldMat = Light.HELP_MAT;
            Matrix.ComposeToRef(Light.HELP_VEC3_ONE, wRot, Light.HELP_VEC3_ZERO, light2WorldMat);
            const lightViewMat = Light.HELP_MAT_1;
            light2WorldMat.invertToRef(lightViewMat);
            for (let i = 0, len = camFrustum.length; i < len; i++) {
                const pos = camFrustum[i];
                const nPos = tempV3;
                Vector3.TransformCoordinatesToRef(pos, lightViewMat, nPos);
                //筛选 near far
                if (nPos.z > max.z) { max.z = nPos.z; }
                if (nPos.z < min.z) { min.z = nPos.z; }
                //筛选 w h
                if (nPos.x > max.x) { max.x = nPos.x; }
                if (nPos.x < min.x) { min.x = nPos.x; }
                if (nPos.y > max.y) { max.y = nPos.y; }
                if (nPos.y < min.y) { min.y = nPos.y; }
            }
            const center = tempV3;
            min.addToRef(max, center);
            center.scaleToRef(0.5, center);
            const offset = center;
            offset.z = min.z;
            const near = 0;
            const far = max.z - min.z;
            let w = max.x - min.x;
            let h = max.y - min.y;

            //
            out.near = near;
            out.far = far;
            out.w = w;
            out.h = h;
            Vector3.TransformCoordinatesToRef(offset, light2WorldMat, out.position);
            out.rotation.copyFrom(wRot);
        }

        /**
         * 计算 方向光视锥 顶点数据
         * @param lfData 视锥数据
         * @param outFrustum 相机视锥(世界空间中的8个顶点位置坐标)
         */
        public static calcDirLightFrustumPoints(lfData: LightFrustumData, outFrustum: Vector3[]) {
            const halfW = lfData.w / 2;
            const halfH = lfData.h / 2;
            const wPos = lfData.position;
            const wRot = lfData.rotation;
            const near = lfData.near;
            const far = lfData.far;
            //不管缩放
            const light2WordMat = Matrix.Compose(new Vector3(1, 1, 1), wRot, wPos);

            //
            outFrustum[0].set(-halfW, halfH, near);       //nearLT
            outFrustum[1].set(halfW, halfH, near);        //nearRT
            outFrustum[2].set(halfW, -halfH, near);       //nearRD
            outFrustum[3].set(-halfW, -halfH, near);      //nearLD
            outFrustum[4].set(-halfW, halfH, far);        //farLT
            outFrustum[5].set(halfW, halfH, far);         //farRT
            outFrustum[6].set(halfW, -halfH, far);        //farRD
            outFrustum[7].set(-halfW, -halfH, far);       //farLD

            outFrustum.forEach((v) => {
                Vector3.TransformCoordinatesToRef(v, light2WordMat, v);
            });
        }

        /**
         * 设置平行投影 ViewProjection 矩阵
         * @param index matrix 索引
         * @param orthoW 正交宽度
         * @param orthoH 正交高度度
         * @param far 远平面
         * @param position 起始位置
         */
        public setVPMatrix(index: number, orthoW: number, orthoH: number, far: number, position: Vector3) {
            if (index < 0 || !this.entity || !position) return;
            const viewMat = Light.HELP_MAT;
            const projMat = Light.HELP_MAT_1;
            const pos = position;
            const rot = this.entity.transform.rotation;
            //view mat
            Matrix.ComposeToRef(Light.HELP_VEC3_ONE, rot, pos, viewMat);
            viewMat.invertToRef(viewMat);
            //p mat
            Matrix.OrthoLHToRef(orthoW, orthoH, this.shadowNear, far, projMat);
            viewMat.multiplyToRef(projMat, this.getViewProjMatrix(index) as Matrix);
        }

        public dispose(): void {
            super.dispose();
            if (this._eventDisp) {
                this._eventDisp.offAll();
            }

            (this._eventDisp as any) = null;
        }

        public addListener<K extends (keyof ILightEventMap)>(eventType: K, listener: (ev: ILightEventMap[K]) => any, thisArg: any): void {
            this._eventDisp.on(eventType, listener, thisArg);
        }

        public removeListener<K extends (keyof ILightEventMap)>(eventType: K, listener: any, thisArg: any): void {
            this._eventDisp.off(eventType, listener, thisArg);
        }
    }
}